const { Arrangement } = require('../algorithm/arrangement.js');
const { ArrangementInfinite } = require('../algorithm/arrangement-infinite.js');
const HashMap = require('../algorithm/hashmap.js');

/**
 * 算24点
 */
class Count24 {
    /**
     * 4个10以内的数字
     * @param {*} nums
     */
    constructor() {
        this.calcs = ArrangementInfinite(['+', '-', '*', '/'], 3);

        const cardDeck = new CardDeck();
        this.cards = cardDeck.newRandomDeck();

        this.arrangements = [];
        this.arrStep1 = [];
        this.arrStep2 = [];
        this.limitCount = 3; // 最多弹出3个算式
        this.finalResults = new HashMap();
    }

    oneRound() {
        if (this.cards.length == 0) {
            console.log('[Deck Round] No more Cards!!!');
            return;
        }

        let arr = [];
        for (let i = 0; i < 4; i++) {
            arr.push(this.cards.shift());
        }

        console.log(`[Count 24] ${JSON.stringify(arr)}`);

        // 下面这段全排列的代码，注释掉
        // 仔细想了一下，不需要排列
        // 比如 1,2,3,4的顺序可以按照4则运算计算出来，那4,3,2,1的顺序一定可以

        // [9,4,5,10] 这样的数字，4则运算没法变成24，一定要改变次序

        this.arrangements = Arrangement(arr);
        // this.arrangements = [arr];
        this.arrStep1 = [];
        this.arrStep2 = [];
        this.finalResults = new HashMap();
        this.count();
    }

    oneGame(arr) {
        this.arrangements = Arrangement(arr);
        this.arrStep1 = [];
        this.arrStep2 = [];
        this.finalResults = new HashMap();
        this.count();
    }

    count(nums = null) {
        // nums = [5, 2, 7, 4];
        if (!!nums) {
            console.log(`[Count 24] ${JSON.stringify(nums)}`);
            this.arrangements = Arrangement(nums);
            this.arrStep1 = [];
            this.arrStep2 = [];
            this.finalResults = new HashMap();
        }

        let loopEnd24Flag = false; // 在后续for循环中，如果已经查到3种计算24点的方式，直接循环中断

        for (let i = 0; i < this.arrangements.length; i++) {
            for (let j = 0; j < this.calcs.length; j++) {
                let arrangement = [].concat(this.arrangements[i]);
                let calc = this.calcs[j];

                for (let k = 0; k < calc.length; k++) {
                    let sign = calc[k];
                    arrangement.splice(2 * k + 1, 0, sign);
                }

                this.arrStep1.push(arrangement);
                loopEnd24Flag = this.calculate(arrangement);

                if (loopEnd24Flag) break;
            }

            if (loopEnd24Flag) break;
        }

        if (loopEnd24Flag) {
            this.logAllCalculations();
            return;
        }

        this.addQoutes();
        this.logAllCalculations();
    }

    addQoutes() {
        // 加括号
        // 不是所有的算式都要加括号，之后那些包含乘法和除法的需要加括号

        for (let i = 0; i < this.arrStep1.length; i++) {
            let calc = this.arrStep1[i];
            if (calc.findIndex((ele) => ele === '*' || ele === '/') === -1)
                continue;

            if (calc.findIndex((ele) => ele === '+' || ele === '-') === -1)
                continue;

            // 11种加括号的方式
            let calc1 = [].concat(calc);
            calc1.splice(0, 0, '(');
            calc1.splice(4, 0, ')');

            let calc2 = [].concat(calc);
            calc2.splice(0, 0, '(');
            calc2.splice(4, 0, ')');
            calc2.splice(6, 0, '(');
            calc2.splice(10, 0, ')');

            let calc3 = [].concat(calc);
            calc3.splice(0, 0, '(');
            calc3.splice(4, 0, ')');
            calc3.splice(0, 0, '(');
            calc3.splice(8, 0, ')');

            let calc4 = [].concat(calc);
            calc4.splice(2, 0, '(');
            calc4.splice(6, 0, ')');

            let calc5 = [].concat(calc);
            calc5.splice(2, 0, '(');
            calc5.splice(6, 0, ')');
            calc5.splice(0, 0, '(');
            calc5.splice(8, 0, ')');

            let calc6 = [].concat(calc);
            calc6.splice(2, 0, '(');
            calc6.splice(6, 0, ')');
            calc6.splice(2, 0, '(');
            calc6.splice(10, 0, ')');

            let calc7 = [].concat(calc);
            calc7.splice(4, 0, '(');
            calc7.splice(8, 0, ')');

            // calc8 和 calc2 重复，省略
            // let calc8 = [].concat(calc);
            // calc1.splice(4, 0, '(');
            // calc1.splice(8, 0, ')');
            // calc1.splice(0, 0, '(');
            // calc1.splice(4, 0, ')');

            let calc9 = [].concat(calc);
            calc9.splice(4, 0, '(');
            calc9.splice(8, 0, ')');
            calc9.splice(2, 0, '(');
            calc9.splice(10, 0, ')');

            let calc10 = [].concat(calc);
            calc10.splice(0, 0, '(');
            calc10.splice(6, 0, ')');

            let calc11 = [].concat(calc);
            calc11.splice(2, 0, '(');
            calc11.splice(8, 0, ')');

            // this.arrStep2.push(calc1);
            // this.arrStep2.push(calc2);
            // this.arrStep2.push(calc3);
            // this.arrStep2.push(calc4);
            // this.arrStep2.push(calc5);
            // this.arrStep2.push(calc6);
            // this.arrStep2.push(calc7);
            // this.arrStep2.push(calc9);
            // this.arrStep2.push(calc10);
            // this.arrStep2.push(calc11);

            this.calculate(calc1);
            this.calculate(calc2);
            this.calculate(calc3);
            this.calculate(calc4);
            this.calculate(calc5);
            this.calculate(calc6);
            this.calculate(calc7);
            this.calculate(calc9);
            this.calculate(calc10);

            if (this.calculate(calc11)) {
                break;
            }
        }
    }

    calculate(arr) {
        let strEquation = arr.join('');
        let result = eval(strEquation);
        if (isNaN(result) || Math.abs(result - 24) > 1e-6) {
            return this.finalResults.keys().length >= this.limitCount;
        }

        let qc = 0; // 括号数量
        let pc = 0; // 加号数量
        let mc = 0; // 减号数量
        let muc = 0; // 乘号数量
        let dc = 0; // 除号数量

        for (let e of arr) {
            switch (e) {
                case '(':
                    qc++;
                    break;
                case '+':
                    pc++;
                    break;
                case '-':
                    mc++;
                    break;
                case '*':
                    muc++;
                    break;
                case '/':
                    dc++;
                    break;
                default:
                    break;
            }
        }

        if (qc > 0) {
            let updateStatus = this.clearQoute(qc, arr);
            qc = updateStatus.qc;
            strEquation = updateStatus.equation;
        }

        this.finalResults.add(`q${qc}p${pc}m${mc}mu${muc}d${dc}`, strEquation);

        return this.finalResults.keys().length >= this.limitCount;
    }

    /**
     * 展开括号，如果把括号去掉，也能算出24，那就去掉括号
     * 注意，这个方法的目的，是为了去除多余的括号，比如(1+2)=3 和 1+2+3这样的
     * 只是去掉括号，但不会改变运算符号
     * @param {*} qc 括号对的数量，数量在[1,2]
     * @param {*} arr 运算数组
     * @returns 满足24点的字符串运算表达式，如果不能
     */
    clearQoute(qc, arr) {
        let arr1 = [].concat(arr);

        arr1.splice(arr1.lastIndexOf('('), 1);
        arr1.splice(arr1.indexOf(')'), 1);

        let arr1Equation = arr1.join('');
        let arr1Result =
            isNaN(eval(arr1Equation)) ||
            Math.abs(eval(arr1Equation) - 24) < 1e-6;

        let arr3 = [].concat(arr);

        arr3.splice(arr3.indexOf('('), 1);
        arr3.splice(arr3.lastIndexOf(')'), 1);

        let arr3Equation = arr3.join('');
        let arr3Result = false;
        try {
            arr3Result =
                isNaN(eval(arr3Equation)) ||
                Math.abs(eval(arr3Equation) - 24) < 1e-6;
        } catch (ex) {
            // console.log(`${arr3Equation} is not a equation`);
            arr3Result = false;
        }

        if (!arr1Result && !arr3Result) {
            return { qc, equation: arr.join('') };
        }

        if (arr1Result && qc === 2) {
            let arr2 = [].concat(arr1);
            arr2.splice(arr2.indexOf('('), 1);
            arr2.splice(arr2.indexOf(')'), 1);

            let arr2Equation = arr2.join('');
            let arr2Result =
                isNaN(eval(arr2Equation)) ||
                Math.abs(eval(arr2Equation) - 24) < 1e-6;

            if (arr2Result) {
                return { qc: 0, equation: arr2Equation };
            } else {
                return { qc: 1, equation: arr1Equation };
            }
        }

        if (arr3Result && qc === 2) {
            let arr2 = [].concat(arr3);
            arr2.splice(arr2.indexOf('('), 1);
            arr2.splice(arr2.indexOf(')'), 1);

            let arr2Equation = arr2.join('');
            let arr2Result =
                isNaN(eval(arr2Equation)) ||
                Math.abs(eval(arr2Equation) - 24) < 1e-6;

            if (arr2Result) {
                return { qc: 0, equation: arr2Equation };
            } else {
                return { qc: 1, equation: arr3Equation };
            }
        }

        return arr1Result
            ? { qc: 0, equation: arr1Equation }
            : { qc: 0, equation: arr3Equation };
    }

    addFinalResult(id, equation) {
        this.resCount.add(id, equation);

        return this.resCount.keys().length;
    }

    logAllCalculations() {
        let count = 0;
        let values = this.finalResults.values();

        let limit = Math.min(values.length, this.limitCount);

        if (limit == 0) {
            console.log(`[Count 24] No equation found`);
        }

        for (let i = 0; i < limit; i++) {
            console.log(values[i]);
        }
    }
}

class CardDeck {
    constructor() {
        this.cards = this.newDeck();
    }
    newDeck() {
        let cards = [];
        for (let i = 1; i <= 10; i++) {
            for (let j = 0; j < 4; j++) {
                cards.push(i);
            }
        }

        return cards;
    }

    /**
     * 一副打乱的牌
     */
    newRandomDeck() {
        let randomDeck = [];
        let { cards } = this;
        while (cards.length > 0) {
            const index = Math.floor(Math.random() * cards.length);
            let randomCard = cards[index];
            cards.splice(index, 1);
            randomDeck.push(randomCard);
        }

        return randomDeck;
    }
}

let c = new Count24();
let testArr = [];
// testArr = [6, 2, 1, 4];

if (testArr.length > 0) {
    c.oneGame(testArr);
} else {
    for (let i = 0; i < 10; i++) {
        console.log(`---------[Deck Round ${i + 1}]--------`);
        c.oneRound();
    }
}
